﻿
#include "DirMonitorForWin.h"

#ifdef Q_OS_WIN

DirMonitorForWin::DirMonitorForWin(QObject *parent)
    : DirMonitor(parent)
    , m_isInWatching(false)
{

    QTimer *timer = new QTimer(this);
    timer->setInterval(1000);

    connect(this, &DirMonitorForWin::fileModified, [this](const QString &path) {
        QFileInfo info(path);
        if (info.isDir()) {
            return;
        }
        if (m_fileClosedDetectList.contains(path)) {
            return;
        }

        m_fileClosedDetectList.append(path);
    });
    connect(timer, &QTimer::timeout, this, &DirMonitorForWin::detectFileClosed);

    // post init
    timer->start();
}

DirMonitorForWin::~DirMonitorForWin()
{
    m_isInWatching = false;
    m_fut.waitForFinished();
}

void DirMonitorForWin::SetRootDirPath(const QString &path)
{
    if (path == m_dirPath) {
        return;
    }

    m_isInWatching = false;
    m_fut.waitForFinished();

    m_isInWatching = true;
    m_fut = QtConcurrent::run(this, &DirMonitorForWin::watchDirInThread, path);
}

void DirMonitorForWin::watchDirInThread(const QString &dirPath)
{
    HANDLE dirHandle = CreateFileW(dirPath.toStdWString().c_str(),
                                GENERIC_READ | GENERIC_WRITE | FILE_LIST_DIRECTORY,
                                FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                NULL,
                                OPEN_EXISTING,
                                FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                                NULL);
     if (dirHandle == INVALID_HANDLE_VALUE) {
         qCritical() << "Failed to get handle of directory.path:" << dirPath
             << " errCode:" << GetLastError();
         return;
     }

    TCHAR notify[1024];
    memset(notify, 0, sizeof(notify));
    FILE_NOTIFY_INFORMATION *pNotification = (FILE_NOTIFY_INFORMATION *)notify;
    DWORD BytesReturned = 0;
    OVERLAPPED ol;
    HANDLE hWatchEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
    ol.hEvent = hWatchEvent;

    while(1) {
        ZeroMemory(pNotification, sizeof(notify));
        auto watch_state = ReadDirectoryChangesW(dirHandle,
                                                 pNotification,
                                                 sizeof(notify),
                                                 TRUE,   //监控子目录
                                                 FILE_NOTIFY_CHANGE_FILE_NAME |
                                                 FILE_NOTIFY_CHANGE_DIR_NAME |
                                                 FILE_NOTIFY_CHANGE_ATTRIBUTES |
                                                 FILE_NOTIFY_CHANGE_SIZE |
                                                 FILE_NOTIFY_CHANGE_CREATION |
                                                 FILE_NOTIFY_CHANGE_LAST_ACCESS |
                                                 FILE_NOTIFY_CHANGE_LAST_WRITE,
                                                 (LPDWORD)&BytesReturned,
                                                 &ol,
                                                 NULL);
        if (GetLastError() == ERROR_INVALID_FUNCTION) {
            qCritical() << u8"文件监控，系统不支持! path:" << "watchedDir";
            break;
        }
        if (watch_state == FALSE) {
            qCritical() << u8"文件监控，监控失败! path:" << "watchedDir"
                << " errCode:" << GetLastError();
            QThread::currentThread()->msleep(2000);
            continue;
        }
        if (GetLastError() == ERROR_NOTIFY_ENUM_DIR) {
            qCritical() << u8"文件监控，内存溢出! path:" << "watchedDir";
            break;
        }

        bool quit = false;
        while(1) {
            DWORD dwRet = WaitForSingleObject(ol.hEvent, 200);
            if (dwRet == WAIT_TIMEOUT and m_isInWatching == false) {
                quit = true;
                break;
            }

            if (dwRet != WAIT_TIMEOUT) {
                break;
            }
        }
        if (quit) {
            break;
        }

        FILE_NOTIFY_INFORMATION *pNotificationTmp = pNotification;
        for (;;) {
            //这里主要就是检测返回的信息，(FILE_NOTIFY_INFORMATION)
            std::wstring fileName(pNotificationTmp->FileName, pNotificationTmp->FileNameLength / sizeof(wchar_t));
            QString str = QString::fromWCharArray(fileName.c_str(), static_cast<int>(fileName.length()));
            QString filePath = QDir::cleanPath(dirPath + "/" + str);
            switch (pNotificationTmp->Action) {
            case FILE_ACTION_ADDED:
            {
                // to do ...
                qDebug() << "Create file " << fileName;
                Q_EMIT FileCreated(filePath);
                break;
            }
            case FILE_ACTION_REMOVED:
            {
                // to do ...
                qDebug() << "Delete file " << fileName;
                Q_EMIT FileDeleted(filePath);
                break;
            }
            case FILE_ACTION_MODIFIED:
            {
                // to do ...
                qDebug() << "Modify file " << fileName << (filePath);
                Q_EMIT fileModified(filePath);
                break;
            }
            case FILE_ACTION_RENAMED_OLD_NAME:
            {
                // to do ...
                qDebug() << "Renamed file old name" << fileName;
                Q_EMIT FileDeleted(filePath);
                break;
            }
            case FILE_ACTION_RENAMED_NEW_NAME:
            {
                // to do ...
                qDebug() << "Renamed file new name " << fileName;
                Q_EMIT FileClosed(filePath);
                break;
            }
            default:
                break;
            }

            if (pNotificationTmp->NextEntryOffset)
                pNotificationTmp = (PFILE_NOTIFY_INFORMATION)((BYTE*)pNotificationTmp + pNotificationTmp->NextEntryOffset);
            else
                break;
        }
    }

    ResetEvent(ol.hEvent);
    CloseHandle(ol.hEvent);
    CloseHandle(dirHandle);
}

void DirMonitorForWin::detectFileClosed()
{
    if (m_fileClosedDetectList.isEmpty()) {
        return;
    }

    QList<QString> listNeedRm;
    for (const QString &path : m_fileClosedDetectList) {
        QFile f(path);
        if (f.open(QIODevice::OpenModeFlag::ReadWrite)) {
            f.close();
            listNeedRm.append(path);
            Q_EMIT FileClosed(path);
        }
    }

    // remove
    for (const QString &path : listNeedRm) {
        m_fileClosedDetectList.removeAll(path);
    }
}

#endif
